package impl

import (
	"context"
	"errors"
	"io"
	"os"
	"strings"

	"atomgit.com/openlinksaas/extension-proto-gen-go.git/extension_fs_api"
)

type ApiImpl struct {
	extension_fs_api.UnimplementedExtensionFsApiServer
	dataDir string
}

func NewApiImpl(dataDir string) (*ApiImpl, error) {
	os.MkdirAll(dataDir, 0700) //skip error check
	stat, err := os.Stat(dataDir)
	if err != nil {
		return nil, err
	}
	if !stat.IsDir() {
		return nil, errors.New("not dir")
	}
	return &ApiImpl{dataDir: dataDir}, nil
}

func (apiImpl *ApiImpl) ReadFile(req *extension_fs_api.ReadFileRequest, s extension_fs_api.ExtensionFsApi_ReadFileServer) error {
	realPath := strings.Join([]string{apiImpl.dataDir, req.FsId, req.FileId}, string(os.PathSeparator))
	f, err := os.Open(realPath)
	if err != nil {
		return err
	}
	defer f.Close()

	buf := make([]byte, 1024*1024)
	for {
		n, err := f.Read(buf)
		if n > 0 {
			s.Send(&extension_fs_api.ReadFileResponse{
				Data: buf[:n],
			})
		}
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
	}
	return nil
}

func (apiImpl *ApiImpl) WriteFile(s extension_fs_api.ExtensionFsApi_WriteFileServer) error {
	var f *os.File
	var realFile string
	defer func() {
		if f != nil {
			f.Close()
			f = nil
		}
	}()

	for {
		req, err := s.Recv()
		if err == io.EOF {
			break
		}
		if err != nil {
			return err
		}
		if f == nil {
			fsPath := strings.Join([]string{apiImpl.dataDir, req.FsId}, string(os.PathSeparator))
			os.MkdirAll(fsPath, 0700) //skip error check
			tmpFile := strings.Join([]string{fsPath, "." + req.FileId}, string(os.PathSeparator))
			realFile = strings.Join([]string{fsPath, req.FileId}, string(os.PathSeparator))
			f, err = os.OpenFile(tmpFile, os.O_CREATE|os.O_APPEND|os.O_WRONLY|os.O_TRUNC, 0600)
			if err != nil {
				return err
			}
		}
		_, err = f.Write(req.Data)
		if err != nil {
			return err
		}
	}
	if f == nil {
		return nil
	}
	err := f.Sync()
	if err != nil {
		return err
	}
	err = os.Rename(f.Name(), realFile)
	if err != nil {
		return err
	}
	s.SendAndClose(&extension_fs_api.WriteFileResponse{})
	return nil
}

func (apiImpl *ApiImpl) CopyFile(ctx context.Context, req *extension_fs_api.CopyFileRequest) (*extension_fs_api.CopyFileResponse, error) {
	fromPath := strings.Join([]string{apiImpl.dataDir, req.FromFsId, req.FromFileId}, string(os.PathSeparator))
	toDirPath := strings.Join([]string{apiImpl.dataDir, req.ToFsId}, string(os.PathSeparator))
	os.MkdirAll(toDirPath, 0700) //skip error check
	fromF, err := os.Open(fromPath)
	if err != nil {
		return nil, err
	}
	defer fromF.Close()

	tmpPath := strings.Join([]string{apiImpl.dataDir, req.ToFsId, "." + req.ToFileId}, string(os.PathSeparator))
	tmpF, err := os.OpenFile(tmpPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY|os.O_TRUNC, 0600)
	if err != nil {
		return nil, err
	}
	defer tmpF.Close()

	_, err = io.Copy(tmpF, fromF)
	if err != nil {
		return nil, err
	}
	err = tmpF.Sync()
	if err != nil {
		return nil, err
	}
	toPath := strings.Join([]string{apiImpl.dataDir, req.ToFsId, req.ToFileId}, string(os.PathSeparator))
	err = os.Rename(tmpPath, toPath)
	if err != nil {
		return nil, err
	}
	return &extension_fs_api.CopyFileResponse{}, nil
}

func (apiImpl *ApiImpl) RemoveFile(ctx context.Context, req *extension_fs_api.RemoveFileRequest) (*extension_fs_api.RemoveFileResponse, error) {
	realPath := strings.Join([]string{apiImpl.dataDir, req.FsId, req.FileId}, string(os.PathSeparator))
	err := os.Remove(realPath)
	if err != nil {
		return nil, err
	}
	return &extension_fs_api.RemoveFileResponse{}, nil
}
